/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package com.model.service;

import com.context.AppContext;
import com.context.properties.EnahoProperties;
import util.log.MiLogger;
import com.context.conexion.EnahoConexion;
import com.model.domain.proceso.envio.carga.EstadoCarga;
import com.model.domain.proceso.envio.carga.LogXmlCargado;
import com.model.domain.proceso.envio.carga.LogXmlCargadoError;
import com.model.domain.proceso.envio.carga.OrigenCarga;
import com.model.domain.proceso.envio.subida.LogXmlTransferido;
import com.model.domain.proceso.envio.carga.ProcesoCarga;
import com.model.domain.proceso.envio.carga.TipoCarga;
import com.model.domain.proceso.envio.subida.xml.ArchivoXmlEnaho;
import util.www.sql.domain.TablaSql;
import util.www.sql.domain.ValorSql;
import java.io.File;
import java.sql.Connection;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.w3c.dom.Node;
import util.file.xml.TablaXml;
import com.model.domain.proceso.envio.subida.xml.TipoArchivoXmlEnaho;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import util.exception.ExceptionUtil;
import util.FormatUtil;
import util.StringUtil;
import util.file.apache_commons.FileUploadUtil;
import util.file.Archivo;
import util.file.xml.ArchivoXml;
import util.file.OperacionSubida;
import util.file.xml.RegistroXml;
import util.log.MiLoggerFile;

/**
 *
 * @author rdelacruz
 */
public abstract class AbstractDataXmlService 
extends AbstractService
{
    
    private LogXmlCargadoService logXmlCargadoService;
    private LogXmlTransferidoService logXmlTransferidoService;
    
    
     
    public AbstractDataXmlService() {
        
    }
            
    //---------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------
    //                  CARGAR ARCHIVOS EN BD
    //---------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------
    
    protected abstract TipoArchivoXmlEnaho getTipoArchivoXmlEnaho();
    protected abstract void cargarDataXML(Connection con, MiLogger logger, LogXmlCargado logxmlcargado) throws Exception;
    
    
     /**
     * Realizar la carga de la data XML de una lista de archivos subidos
     * Se utiliza para cargar los archivos XML indicados por un usuario
     * @param logger
     * @param archivos lista de archivos subidos
     * @param conControlEnvio indica si se debe realizar el control de envio a nivel de cada hogar
     * @param usuario es el identificador del usuario que realiza esta operacion
     * @throws Exception 
     */
    public final void cargarDataXML_ManualMenu(MiLogger logger, List<LogXmlTransferido> archivos, boolean conControlEnvio, String usuario) throws Exception {
        
        
        ProcesoCarga p = new ProcesoCarga();
        p.setConControlEnvioHogar(conControlEnvio);
        p.setTipo(TipoCarga.MANUAL_MENU);
        p.setUsuario(usuario);
        logger.logd("2.1 CARGA MANUAL DE ARCHIVOS XML EN BD INICIADA");
        
        cargarDataXML(p, logger, archivos);//CARGA
        
        logger.logd("2.1 CARGA MANUAL DE ARCHIVOS XML EN BD TERMINADA");
    }
        
    /**
     * Realiza la carga de la data XML que tiene activado el flag Existe
     * y desactivado el flag procesoCarga.
     * Este método debe ser llamado por un proceso automático programado 
     * para ejecutarse cada cierto tiempo  
     * @param logger
     * @param usuario
     */
    public void cargarDataXML_Automatica(MiLogger logger, String usuario) {
        
        AppContext app = AppContext.getInstancia();            
        MiLoggerFile loggerCargaAutomatica = app.getLoggerContext().getLoggerCargaAutomatica();
        MiLogger loggerProceso = logger;
        if(loggerProceso==null) {
            loggerProceso = loggerCargaAutomatica;
        }
        else {
            loggerProceso.addLogger(loggerCargaAutomatica);
        }
        
        ProcesoCarga p = new ProcesoCarga();
        p.setConControlEnvioHogar(true);
        p.setTipo(TipoCarga.AUTOMATICA);
        p.setUsuario(usuario);
        List<LogXmlTransferido> archivos = null; 
        
        Connection con = null;
        
        try {
            con = EnahoConexion.getConexion();                                  
            
            Integer NRO_MAXIMO_ARCHIVOS_CARGAR = null;
            
            //NRO_MAXIMO_ARCHIVOS_CARGAR = 20;
            
            archivos = this.getLogXmlTransferidoService().findLogXmlsExistentesSinProcesoDeCargaByTipo(con,
                    loggerProceso, 
                    getTipoArchivoXmlEnaho(), 
                    OrigenCarga.BASE_DATOS,
                    NRO_MAXIMO_ARCHIVOS_CARGAR);
            
            loggerProceso.logm("******************************************************************");            
            loggerProceso.logm("CARGA AUTOMATICA - INICIO ("+FormatUtil.format(new Date())+")");
            loggerProceso.logm("Se intentarán cargar "+archivos.size()+" archivos");
                                      
            int n = cargarDataXML(p, loggerProceso, archivos);
            
            loggerProceso.logm("Resultado de la carga [Procesados="+n+", No Procesados="+(archivos.size() - n)+"]");            
        }
        catch(Exception ex) {
            if(loggerProceso!=null) loggerProceso.loge("DURANTE LA CARGA AUTOMATICA", ex);
        }
        finally {            
            if(con!=null) try {
                con.close();
            } catch (SQLException ex) {
                loggerProceso.loge("No se pudo cerrar la conexion",ex);
            }
            loggerProceso.logm("CARGA AUTOMATICA - FIN ("+FormatUtil.format(new Date())+")");
            loggerProceso.logm("******************************************************************");
            
            if(logger!=null) {
                String url = loggerCargaAutomatica.getFileLog().getPath();
                logger.logm("Ver los detalles en "+url+" y en la opción 'Recuperacion de Envios'", false);            
            }
        }
        //return archivos;
    }

    /**
     * Carga una lista de archivos. 
     * @param proceso
     * @param logger
     * @param archivos
     * @return Retorna el numero de archivos cargados
     * @throws Exception 
     */
    private int cargarDataXML(ProcesoCarga proceso, MiLogger logger, List<LogXmlTransferido> archivos) throws Exception {
        int n = 0;
        
        Connection con = null;
        try {
            //logger.logd("obteniendo conexion");            
            con = EnahoConexion.getConexion(); 
            logger.logd("conexion obtenida "+con);
            
            for(LogXmlTransferido logxml : archivos) {  
                logger.log("---------------------------------------------------");
                cargarDataXML_unSoloArchivo(proceso, con,logger,logxml);
                if(logxml.isProcesoCargaRealizado())
                    n++;
            }
            logger.log("---------------------------------------------------");
        }
        finally {            
            if(con!=null) con.close();                  
        }
        return n;
    }
    
    /**
     * Carga un solo archivo XML subido anteriormente, sin lanzar excepciones.
     * NOTA: Este metodo no debe lanzar una EXCEPCION para asegurar que si falla una carga, el resto continue
     * @param proceso
     * @param con
     * @param logger
     * @param logXml
     */  
    protected final void cargarDataXML_unSoloArchivo(ProcesoCarga proceso, Connection con, MiLogger logger, LogXmlTransferido logXml) {
        logXml.setProcesoCargaRealizado(false);

        if(!logXml.existeArchivoTransferido())
            return ;
        
        String archivoXml = logXml.getOperacionSubida().getArchivo().getUrl();
        LogXmlCargado logXmlCargado = new LogXmlCargado();
        
        try {                            
            logXmlCargado.setLogXmlTransferido(logXml);
            logXmlCargado.setProcesoCarga(proceso);                
            
            //File fileXML = new File(archivoXml);
            //ArchivoXmlEnaho archivoXmlEnaho = ArchivoXmlEnaho.parsear(fileXML);
            
            ArchivoXmlEnaho archivoXmlEnaho = logXml.getArchivoXmlEnaho();
            logXml.setArchivoXmlEnaho(archivoXmlEnaho);
            
            //CARGA ABSTRACTA
            //logger.logd("cargarDataXML_sinInterrupciones");
            cargarDataXML(con,logger,logXmlCargado);

            logXml.setProcesoCargaRealizado(true);
            logger.logm("Se realizó el proceso de carga de '"+archivoXml+"'");

            //aqui se llega si no hubo excepciones anteriormente             
            try {                                
                logXmlCargadoService.save(con,logger,logXmlCargado);
            }
            catch(SQLException ex) {
                throw new Exception("Si se cargó '"+archivoXml+"' en base de datos, pero hubo errores al registrar el LOG",ex);
            }            
        }      
        catch(Exception ex) {
            Exception ex_carga;
            if(logXml.isProcesoCargaRealizado()) {
                ex_carga = ex;
            }
            else {
                ex_carga = new Exception("No se pudo cargar '"+archivoXml+"' en base de datos",ex);                
            }       
            
            saveErrorCarga(con, ex_carga, logger, logXmlCargado);            
        } 
    }
    
    //Este metodo NO debe lanzar una excepcion, por eso le hago try a todo
    private void saveErrorCarga(Connection con, Exception ex, MiLogger logger, LogXmlCargado logXmlCargado) {        
        try {
            //En archivo            
            logger.loge(ex);
            //AppContext.getInstancia().getLoggerContext().getLoggerErrores().loge(ex);
        
            //En tabla
            LogXmlCargadoError loge = new LogXmlCargadoError();
            loge.setLogXmlCargado(logXmlCargado);
            loge.setError(ExceptionUtil.getStackTrace(ex));        
            logXmlCargadoService.getLogXmlCargadoErrorService().save(con,loge);
        } catch (SQLException ex1) {
            logger.loge(ex1);
        }
    }
    

    protected final void verificarSiTablasXML_TablasSQL_tienenMismoNombre(Connection con, MiLogger logger, LogXmlTransferido logxml) throws Exception {
        

        //VERIFICANDO si todas las tablas del XML existen en la BD SQL            
        ArchivoXmlEnaho archivoXml = logxml.getArchivoXmlEnaho();
        Map<String,Node> nodosTablaXML = archivoXml.getNodosTabla();
        for(Map.Entry<String,Node> e: nodosTablaXML.entrySet()) {            
            String tabla = e.getKey();
            TablaSql tsql = EnahoConexion.getTablaEnaho(con, tabla);
            if(tsql==null) {
                logger.loge(logxml.getOperacionSubida().getArchivo().getNombre()+": Tabla '"+tabla+"' del XML no existe en la base de datos SQL");
            }
        }
    }    
   
    protected final void cargarDataXML_Tabla(Connection con, MiLogger logger, LogXmlCargado logxmlcargado, String tabla) throws Exception {
        cargarDataXML_Tabla(con,logger, logxmlcargado,tabla,false);
    }
    
    protected final void cargarDataXML_Tabla(Connection con, MiLogger logger, LogXmlCargado logxmlcargado, String tabla, boolean forzarCarga) throws Exception {
        //logger.logd("cargarDataXML_Tabla 1");
        cargarDataXML_Tabla(forzarCarga, logger, logxmlcargado,con,tabla,tabla);
    }
    
    /**
     *  Revisa cada nodo Tabla del XML, luego revisa cada registro de la tabla XML. 
     *  Despues revisa si el registro ya ha sido insertado anteriormente. 
     *  Si es que es si, aplica UPDATE, sino INSERT en su respectiva tabla SQL
     * 
     * @param logger objeto log para mostrar un mensaje en el JSP y/o consola
     * @param archivoXML objeto archivo del XML
     * @param con la conexion
     * @param nodosTabla los nodos tabla como nodos DOM
     * @param tablaXml nombre de la tabla en el XML
     * @param tablaSql nombre de la tabla en el SQL Server
     * @throws Exception 
     */
    //private void cargarDataXML_Tabla(ProcesoCarga proceso, Logger logger, File archivoXML, Connection con, Map<String,Node> nodosTabla, String tablaXml, String tablaSql) 
    private void cargarDataXML_Tabla(boolean forzarCarga, MiLogger logger, LogXmlCargado logxmlcargado, Connection con, String tablaXml, String tablaSql) 
            throws Exception {    
        
        //logger.logd("TABLA "+tablaXml);
                
        LogXmlTransferido logxml = logxmlcargado.getLogXmlTransferido();
        ArchivoXmlEnaho archivoXml = logxml.getArchivoXmlEnaho();
                        
        TablaXml<RegistroXml> txml = new TablaXml<RegistroXml>();
        if(!archivoXml.parsear(txml,tablaXml)) {
            logger.logd("No existe tabla '"+tablaXml+"' en archivo XML '"+logxml.getOperacionSubida().getArchivo().getUrl()+"'");
        }        
             
        
        //String tablaXml = txml.getNombre();        
        TablaSql tsql = null;
        try {
            tsql = EnahoConexion.getTablaEnaho(con,tablaSql);
        } catch (Exception ex) {
            logger.loge("No se pudo obtener tabla '"+tablaSql, ex);
        }
        
        if(tsql!=null) {  
            String[] columnasClave = tsql.getNombreColumnasClave();        
            //logger.logm("Iniciado carga de tabla "+tablaSql);            
            int nregCargados = 0;            
            for(RegistroXml r: txml.getRegistros()) {                                               
                
                Statement stSelect = null;
                Statement stUpdate = null;
                
                String condicionWhere = "";
                        
                try {    
                    logxmlcargado.registrarResultado(r,EstadoCarga.NO_CARGADO);
                    
                    if(!forzarCarga) {
                        if(!logxmlcargado.sePuedeCargarRegistro(r)) {
                            logxmlcargado.registrarResultado(r,EstadoCarga.CARGA_RECHAZADA);
                            continue;
                        }
                    }                                        
                    
                    String condiciones[] = new String[columnasClave.length];                
                    int i = 0;
                    for(String col : columnasClave) {
                        ValorSql v = new ValorSql();
                        v.setColumna(tsql.getColumna(col));
                        v.setValor(r.getValorString(col));
                        condiciones[i] = columnasClave[i].concat("=").concat(v.getValorSql());
                        i++;
                    }
                    condicionWhere = "where "+StringUtil.unirSeparadoPor(condiciones, " and ");

                    //Map<String,Node> nodosTabla = CampoXmlParser.getNodosTabla(doc);
                    String select = "SELECT "+StringUtil.unirSeparadoPor(columnasClave, ",")+
                            " from "+tablaSql+" "+condicionWhere;
                    logger.logd(select);

                    stSelect = con.createStatement();
                    ResultSet rs = stSelect.executeQuery(select);

                    String sql;
                    if(rs.next()) {
                        sql = tsql.scriptUpdate(r,condicionWhere);
                        //Log.logd("UPDATE: "+sql);
                    }
                    else {
                        sql = tsql.scriptInsert(r);
                        //Log.logd("INSERT: "+sql);
                    }
                    logger.logd(sql);

                    stUpdate = con.createStatement();                
                    stUpdate.executeUpdate(sql);
                    
                    logxmlcargado.registrarResultado(r,EstadoCarga.CARGADO);
                    nregCargados++;
                }                 
                //catch(SQLException e) {
                catch(Exception e) {
                    String mensaje = "No se pudo cargar el registro '"+tablaSql+"' "+condicionWhere+". ";
                    try {
                        logxmlcargado.registrarResultado(r,EstadoCarga.CARGADO_CON_ERROR);
                    }
                    catch(Exception e2) {
                        mensaje += e2.getMessage();
                    }                    
                    Exception eFinal = new Exception(mensaje,e);
                    saveErrorCarga(con, eFinal, logger, logxmlcargado);
                }
                finally {
                    try {
                        if(stSelect!=null) stSelect.close();
                        if(stUpdate!=null) stUpdate.close();
                    } catch (SQLException ex) {
                        logger.loge("No se pudo cerrar la conexion Statement",ex);
                    }
                }
            }
            
            int nregNoCargados = txml.getRegistros().size() - nregCargados;
            //String separacion = StringUtil.repetirTexto(" ", 40 - tablaXml.length());
            String separacion = ": ";
            logger.logd("Tabla '"+tablaXml+"'"+separacion+"Registros (cargados = "+nregCargados+", no cargados = "+nregNoCargados+" )");
        }
        else {
            logger.loge("No existe tabla '"+tablaSql+"' en base de datos SQL");
        }
    }

    
    
    
    //---------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------
    //                  TRANSFERENCIA DE ARCHIVOS
    //---------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------
    
    protected void validacionArchivoXml(Connection con, MiLogger logger, ArchivoXmlEnaho arch, Archivo a) throws Exception {
        if(arch.getTipo() != this.getTipoArchivoXmlEnaho()) {
            throw new Exception("'"+a.getNombre()+"' no fue subido porque no es el correcto para esta opción.");
        }
    }

    /**
     * 
     * @param request
     * @param lista (output)
     * @param logger
     * @param usuario
     * @param ip
     * @throws Exception 
     */
    public void transferirDataXML(
            HttpServletRequest request, 
            List<LogXmlTransferido> lista, 
            MiLogger logger, 
            String usuario, 
            String ip) throws Exception {       
        
        
        
        
        Connection con = null;
        try {
            con = EnahoConexion.getConexion();
            
            //----------------------------------------
            //1.1. SUBIENDO ARCHIVOS AL SERVIDOR
            //----------------------------------------
            EnahoProperties prop = AppContext.getInstancia().getEnahoProperties(con);

            List<OperacionSubida> operaciones;
            try {            
                operaciones = FileUploadUtil.subirArchivos(
                        request, 
                        prop.getDirectorio_upload(),
                        prop.getDirectorio_upload(), 
                        true,
                        true);            
            }
            finally {
                logger.logd("1.1. ARCHIVOS TRANSFERIDOS");
            }
            
            //----------------------------------------
            //1.2. VALIDANDO Y CREANDO UNA COPIA DE CADA ARCHIVO SUBIDO
            //----------------------------------------
            for(OperacionSubida op : operaciones) {
                LogXmlTransferido logxml = new LogXmlTransferido();
                logxml.setOperacionSubida(op);
                logxml.setIp(ip);
                logxml.setUsuario(usuario);                
                logxml.setSubido(false);

                Archivo aOrig = op.getArchivo();
                Archivo aTemp = op.getArchivoTemporal();

                if(op.isOk()) {                    
                    File temp = new File(aTemp.getUrl());
                    try {
                        if(temp.exists()) {      
                            ArchivoXml axml = new ArchivoXml(temp);
                            ArchivoXmlEnaho arch = ArchivoXmlEnaho.parsear(axml);                            
                                                            
                            try {
                                //VALIDACION DEL ARCHIVO XML
                                validacionArchivoXml(con, logger,arch, aOrig); 

                                //CREANDO EL ORIGINAL A PARTIR DEL TEMPORAL (ES LA TRANSFERENCIA EN LA PRACTICA)
                                File original = new File(aOrig.getUrl());
                                FileUtils.copyFile(temp, original);

                                //CREANDO UNA COPIA DEL ORIGINAL
                                try {
                                    File directorioCopia = new File(prop.getDirectorio_upload_copia());
                                    FileUtils.copyFileToDirectory(original, directorioCopia);
                                }
                                catch(Exception e) {
                                    //MENSAJE
                                    logger.logm("No se pudo crear una copia de '"+aOrig.getNombre()+"'");
                                }
                                logxml.setArchivoXmlEnaho(arch);
                                //logxml.setValidoParaCargar(true);                                    
                            }
                            catch(Exception e) {
                                //ERROR
                                logger.loge("No se pudo transferir '"+aOrig.getNombre()+"'.", e);

                                //LogXmlTransferidoError loge = new LogXmlTransferidoError();
                                //loge.setLogXmlTransferido(logxml);
                                //loge.setError(ExceptionUtil.getStackTrace(ex));
                                //logXmlTransferidoService.getLogXmlTransferidoErrorService().save(con, loge);
                            }                          

                            //ELIMINANDO EL TEMPORAL DEL ARCHIVO SUBIDO
                            temp.delete();
                        }
                    } catch(Exception ex) {
                        logger.loge("Durante la transferencia de '"+aOrig.getNombre()+"'. Este archivo es inválido. ", ex);

                        //XmlUtil.verSiInputStreamEstaCerrado(aTemp.getUrl());

                        /** NOTA: Si ocurre una excepcion no elimino el temporal,
                         * para luego saber qué archivo se subio incorrectamente
                         */
                        //temp.delete();
                    }  
                } else {
                    logger.logd(op.getMensajeError());
                }

                lista.add(logxml);
            }
            logger.logd("1.2. ARCHIVOS COPIADOS");

            //----------------------------------------
            //1.3. REGISTRANDO ARCHIVO SUBIDO EN BASE DE DATOS
            //----------------------------------------  
            try {
                //GUARDANDO LOGS
                logXmlTransferidoService.save(con,logger, lista);
            }
            finally {            
                logger.logd("1.3. ARCHIVOS REGISTRADOS EN BD");
            }
        }
        finally {
            if(con!=null) con.close();//con.close() debe estar antes que statement.close() para asegurar que se ejecute
        }
    } 
    
     
    

    public LogXmlTransferidoService getLogXmlTransferidoService() {
        return logXmlTransferidoService;
    }

    public void setLogXmlTransferidoService(LogXmlTransferidoService LogXmlTransferidoService) {
        this.logXmlTransferidoService = LogXmlTransferidoService;
    }

    public LogXmlCargadoService getLogXmlCargadoService() {
        return logXmlCargadoService;
    }

    public void setLogXmlCargadoService(LogXmlCargadoService logXmlCargadoService) {
        this.logXmlCargadoService = logXmlCargadoService;
    }    

   

    
    

}
